if not (SERVER) then return end

// Contraption Saver Tool with GPL Elements by TAD2020

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

// Contraption Saver

ContraptionSaver = {}

// Tables

ContraptionSaver.Players = {}
ContraptionSaver.Cached = {}
ContraptionSaver.Config = {}
ContraptionSaver.File = {}

// Include

include("ContraptionSaver/Config.lua")
include("ContraptionSaver/Hooks.lua")
include("ContraptionSaver/Serialise.lua")

// Modules

local Files = file.FindInLua("ContraptionSaver/Modules/*.lua")

for K, V in pairs(Files) do
	include("ContraptionSaver/Modules/"..V)
end

// Select connected

function ContraptionSaver.SelectConnected(Player, Entity)
	local Entities = {}
	local Constraints = {}
	
	// Get all constrained entities and constraints
	
	duplicator.GetAllConstrainedEntitiesAndConstraints(Entity, Entities, Constraints)
	
	// Select all the found entities
	
	for K, V in pairs(Entities) do ContraptionSaver.Select(Player, V) end
end

// Select

function ContraptionSaver.Select(Player, Entity)
	local Index = Entity:EntIndex()
	
	// Check if we already have it selected
	
	if (ContraptionSaver.Players[Player].Entities[Index]) then return end
	
	// Check if there is no other entities
	
	local Head = false
	
	for K, V in pairs(ContraptionSaver.Players[Player].Entities) do
		if (V.ContraptionSaver and V.ContraptionSaver.Head) then
			Head = true
			
			break
		end
	end
	
	// Set it in our table
	
	ContraptionSaver.Players[Player].Entities[Index] = Entity
	
	// Color and material
	
	Entity.ContraptionSaver = {}
	
	// Check if it is the head entity
	
	if (!Head) then Entity.ContraptionSaver.Head = true end
	
	// Set the material and color
	
	local Material = Entity:GetMaterial()
	local R, G, B, A = Entity:GetColor()
	
	Entity.ContraptionSaver.Color = Color(R, G, B, A)
	Entity.ContraptionSaver.Material = Material
	
	// Call on remove
	
	local function Deselect(Entity, Player, Index)
		ContraptionSaver.Players[Player].Entities[Index] = nil
	end
	
	Entity:CallOnRemove("ContraptionSaver", Deselect, Player, Index)
	
	// Change
	
	Entity:SetMaterial(ContraptionSaver.Config.Material)
	
	// Color
	
	local Class = Entity:GetClass()
	
	if (Entity.ContraptionSaver.Head) then Class = "Head" end
	
	if (ContraptionSaver.Config.Selected[Class]) then
		local Color = ContraptionSaver.Config.Selected[Class]
		
		Entity:SetColor(Color.r, Color.g, Color.b, Color.a)
	else
		Entity:SetColor(100, 255, 100, 255)
	end
end

// Deselect

function ContraptionSaver.Deselect(Player, Entity)
	local Index = Entity:EntIndex()
	
	if (ContraptionSaver.Players[Player].Entities[Index]) then
		if (Entity.ContraptionSaver) then
			local Color = Entity.ContraptionSaver.Color
			local Material = Entity.ContraptionSaver.Material
			
			// Material and color
			
			Entity:SetMaterial(Material)
			Entity:SetColor(Color.r, Color.g, Color.b, Color.a)
			
			// Delete the contraption saver table
			
			Entity.ContraptionSaver = nil
		end
		
		// Remove it from our table
		
		ContraptionSaver.Players[Player].Entities[Index] = nil
	end
end

// Clear

function ContraptionSaver.Clear(Player)
	local Count = table.Count(ContraptionSaver.Players[Player].Entities)
	
	// Check
	
	if (Count == 0) then return end
	
	// Loop
	
	for K, V in pairs(ContraptionSaver.Players[Player].Entities) do
		ContraptionSaver.Deselect(Player, V)
	end
	
	// Reset the table
	
	ContraptionSaver.Players[Player].Entities = {}
	
	return Count
end

// Timers

ContraptionSaver.Timers = {}
ContraptionSaver.Timers.List = {}

// Add timer

function ContraptionSaver.Timers.Add(Time, Function, ...)
	Time = CurTime() + Time
	
	table.insert(ContraptionSaver.Timers.List, {Time, Function, arg})
end

// Timers think

function ContraptionSaver.Timers.Think()
	local Cur = CurTime()
	
	for K, V in pairs(ContraptionSaver.Timers.List) do
		if (Cur > V[1]) then
			local B, Error = pcall(V[2], unpack(V[3]))
			
			if (!B) then
				Msg("[Contraption Saver] Timer error: "..Error.."!\n")
			end
			
			ContraptionSaver.Timers.List[K] = nil
		end
	end
end

hook.Add("Think", "ContraptionSaver.Timers.Think", ContraptionSaver.Timers.Think)

// Conver model

function ContraptionSaver.ConvertModel(Model)
	Model = string.Replace(Model, "\\", "/")
	Model = string.Replace(Model, "//", "/")
	
	return Model
end

// Generate directories

function ContraptionSaver.GenerateDirectories(Player)
	umsg.Start("ContraptionSaver.Start", Player) umsg.End()
	
	// Function
	
	local function Usermessage(File)
		File = ContraptionSaver.Defolder(File, 2)
		
		umsg.Start("ContraptionSaver.File", Player) umsg.String(File) umsg.End()
	end
	
	// Recursive find
	
	ContraptionSaver.RecursiveFind(ContraptionSaver.Config.Directory, Usermessage)
	
	umsg.Start("ContraptionSaver.Finish", Player) umsg.End()
end

// Defolder

function ContraptionSaver.Defolder(File, Amount)
	local Explode = string.Explode("/", File)
	
	Explode = table.concat(Explode, "/", Amount)
	
	return Explode
end

// Get file path

function ContraptionSaver.GetFilePath(Player)
	local File = Player:GetInfo("contraptionsaver_File")
	
	local Directory = Player:GetInfo("contraptionsaver_Directory")
	
	// Add trailing slash
	
	if (Directory != "") then
		Directory = ContraptionSaver.CreateTrailingSlash(Directory)
	else
		ContraptionSaver.Message(Player, 'You need to enter a directory name!')
		
		return false
	end
	
	// File
	
	if (File == "") then
		ContraptionSaver.Message(Player, 'You need to enter a file name!')
		
		return false
	end
	
	// Format
	
	File = ContraptionSaver.FormatFile(File)
	Directory = ContraptionSaver.FormatDirectory(Directory)
	
	return File, Directory
end

// Delete from directory

function ContraptionSaver.File.Delete(File)
	file.Delete(File)
end

// Message

function ContraptionSaver.Message(Player, Message)
	Player:SendLua([[GAMEMODE:AddNotify(']]..Message..[[', NOTIFY_GENERIC, 10)]])
	Player:SendLua([[surface.PlaySound('ambient/water/drip]]..math.random(1, 4)..[[.wav')]])
end

// Format file

function ContraptionSaver.FormatFile(File)
	File = string.Replace(File, "\\", "/")
	File = string.Replace(File, "/", "")
	File = string.Replace(File, ":", "")
	File = string.Replace(File, "(", "")
	File = string.Replace(File, ")", "")
	File = string.Replace(File, "*", "")
	File = string.Replace(File, "|", "")
	File = string.Replace(File, ";", "")
	File = string.Replace(File, "'", "")
	File = string.Replace(File, '"', '')
	
	return File
end

// Format directory

function ContraptionSaver.FormatDirectory(Directory)
	Directory = string.Replace(Directory, "\\", "/")
	Directory = string.Replace(Directory, "_", " ")
	Directory = string.Replace(Directory, ".", "")
	Directory = string.Replace(Directory, "(", "")
	Directory = string.Replace(Directory, ")", "")
	Directory = string.Replace(Directory, ":", "")
	Directory = string.Replace(Directory, "|", "")
	Directory = string.Replace(Directory, "*", "")
	Directory = string.Replace(Directory, ";", "")
	Directory = string.Replace(Directory, "'", "")
	Directory = string.Replace(Directory, '"', '')
	
	return Directory
end

// Save to directory

function ContraptionSaver.File.Save(Table, File, Creator, Description)
	// Saveable
	
	local Saveable = ContraptionSaver.ConvertToSaveableTable(Table)
	
	// Header and extra
	
	local Header, Extra = ContraptionSaver.GenerateHeaderInformation(Creator, Description, Saveable)
	
	// Contents
	
	local Contents = Serialise.SerialiseWithHeaders(Header, Extra, Saveable)
	
	// Write
	
	if (File) then
		file.Write(File, Contents)
	else
		return Contents
	end
end

// Generate header information

function ContraptionSaver.GenerateHeaderInformation(Creator, Description, Table)
	// Header
	
	local Header = {}
	
	Header[1] = "Type:Contraption Saver File"
	Header[2] = "Creator:"..string.format("%q", Creator)
	Header[3] = "Date:"..os.date("%m/%d/%y")
	Header[4] = "Description:"..string.format("%q", Description)
	Header[5] = "Entities:"..table.Count(Table.Entities)
	Header[6] = "Constraints:"..table.Count(Table.Constraints)
	
	// Extra
	
	local Extra = {}
	
	Extra[1] = "Version:"..ContraptionSaver.Config.Version
	
	// Return
	
	return Header, Extra
end

// Convert file

function ContraptionSaver.ConvertFile(File, Contents)
	// Contents
	
	Contents = Contents or file.Read(File)
	
	// Table
	
	local Table = ContraptionSaver.KeyValuesToTable(Contents)
	
	// Try deserialising it
	
	if not (Table) then
		Table = Serialise.Deserialise(Contents)
	end
	
	// Check the table
	
	if (Table) then
		// Check information
		
		Table.Information = Table.Information or {}
		Table.Information.Creator = Table.Information.Creator or "Unknown"
		Table.Information.Description = Table.Information.Description or "None"
		Table.Information.Date = Table.Information.Date or os.date("%m/%d/%y")
		Table.Information.Entities = Table.Information.Entities or 0
		Table.Information.Constraints = Table.Information.Constraints or 0
		
		// Convert entity positions
		
		if (Table.Entities) then
			local Head = ContraptionSaver.GetFirstEntry(Table.Entities)
			
			// Get the lowest entity
			
			local Lowest = nil
			local Key = nil
			
			for K, V in pairs(Table.Entities) do
				if not (Lowest) or (V.Pos.z < Lowest) then
					Lowest = V.Pos.z
					
					Head = Table.Entities[K]
				end
			end
			
			// Convert entity positions to local
			
			for K, V in pairs(Table.Entities) do
				Table.Entities[K].LocalPos = Vector(0, 0, 0)
				Table.Entities[K].LocalAngle = Angle(0, 0, 0)
				
				// Check if the entity is the head
				
				if (Head == V) then Table.Head = Head end
				
				// Convert entity positions to local
				
				ContraptionSaver.ConvertEntityPositionToLocal(Head.Pos, Table.Entities[K])
			end
			
			// Convert constraint positions to local
			
			if (Table.Constraints) then
				for K, V in pairs(Table.Constraints) do
					ContraptionSaver.ConvertConstraintPositionToLocal(Head.Pos, Table.Constraints[K])
				end
			end
		end
		
		// Header and extra
		
		local Header, Extra = ContraptionSaver.GenerateHeaderInformation(Table.Information.Creator, Table.Information.Description, Table)
		
		// Contents
		
		local Contents = Serialise.SerialiseWithHeaders(Header, Extra, Table)
		
		// Write it
		
		file.Write(File, Contents)
		
		// Return true
		
		return true
	else
		return false
	end
end

// Check entities are valid

function ContraptionSaver.CheckEntitiesAreValid(Entities)
	for K, V in pairs(Entities) do
		local Valid = V:IsValid()
		
		if not (Valid) then
			Entities[K] = nil
		end
	end
end

// Create trailing slash

function ContraptionSaver.CreateTrailingSlash(Directory)
	Directory = string.Replace(Directory, "/", "")
	
	Directory = Directory.."/"
	
	return Directory
end

// Is directory owner

function ContraptionSaver.IsDirectoryOwner(Player, Directory)
	// Check if they're an admin
	
	local Admin = Player:IsAdmin()
	
	if (Admin) then return true end
	
	// They're not an admin so check if they own it
	
	Directory = string.lower(Directory)
	
	if (ContraptionSaver.Directories[Directory]) then
		local Number = ContraptionSaver.Directories[Directory][2]
		
		if (Number) then
			local Unique = Player:UniqueID()
			
			if (Number != Unique) then
				ContraptionSaver.Message(Player, 'You are not the owner of this directory!')
				
				return false
			end
		end
	end
	
	return true
end

// Save directory owners

function ContraptionSaver.SaveDirectoryOwners()
	for K, V in pairs(ContraptionSaver.Directories) do
		ContraptionSaver.Directories[K] = table.concat(V, "|")
	end
	
	// Concatenate by newline
	
	ContraptionSaver.Directories = table.concat(ContraptionSaver.Directories, "\n")
	
	// Write the file
	
	file.Write(ContraptionSaver.Config.Directory.."/Directories.txt")
end

hook.Add("ShutDown", "ContraptionSaver.SaveDirectoryOwners", ContraptionSaver.SaveDirectoryOwners)

// Set owner of directory

function ContraptionSaver.SetDirectoryOwner(Player, Directory)
	// Set them to be the owner
	
	Directory = string.lower(Directory)
	
	ContraptionSaver.Directories[Directory] = {}
	ContraptionSaver.Directories[Directory][1] = Directory
	ContraptionSaver.Directories[Directory][2] = Player:UniqueID()
end

// Recursive find

function ContraptionSaver.RecursiveFind(Directory, Function)
	local Files = file.Find(Directory.."*")
	
	for K, V in pairs(Files) do
		if (file.IsDir(Directory..V.."/")) then
			ContraptionSaver.RecursiveFind(Directory..V.."/", Function)
		else
			if (V != "." and V != "..") then
				Function(Directory..V)
			end
		end
	end
end

// Create ghost entities

function ContraptionSaver.CreateGhostEntities(Player, Table)
	// Remove ghost entities
	
	ContraptionSaver.RemoveGhostEntities(Player)
	
	// Get first entity
	
	local Head = ContraptionSaver.GetFirstEntry(Table.Entities)
	
	// Get head entity
	
	if (Head) then Head = ContraptionSaver.GetHeadEntity(Head, Table) end
	
	// If we have a head entity then make our ghost entities
	
	if (Head) then
		local Parent = ContraptionSaver.CreateGhostEntity(Head.Model, Head.LocalPos, Head.LocalAngle)
		
		// Ghosts
		
		local Ghosts = {}
		
		// Entities and Count
		
		Ghosts.Entities = {}
		Ghosts.Count = 0
		
		// Create them all
		
		for K, V in pairs(Table.Entities) do
			if (Head == V) then
				Ghosts.Head = {K, Parent}
				
				Ghosts.Entities[K] = Parent
			else
				if (Ghosts.Count < ContraptionSaver.Config.Ghosts or ContraptionSaver.Config.Ghosts == -1) then
					local Entity = ContraptionSaver.CreateGhostEntity(V.Model, V.LocalPos, V.LocalAngle)
					
					if (Entity) then
						Entity:SetParent(Parent)
						
						Ghosts.Entities[K] = Entity
						
						Ghosts.Count = Ghosts.Count + 1
					end
				end
			end
		end
		
		return Ghosts
	else
		return false
	end
end

// Create ghost entity

function ContraptionSaver.CreateGhostEntity(Model, Position, Angle)
	local Entity = ents.Create("prop_physics")
	
	Entity:SetModel(Model)
	Entity:SetPos(Position)
	Entity:SetAngles(Angle)
	Entity:Spawn()
	Entity:DrawShadow(false)
	Entity:SetMoveType(MOVETYPE_NONE)
	Entity:SetSolid(SOLID_VPHYSICS)
	Entity:SetNotSolid(true)
	Entity:SetRenderMode(RENDERMODE_TRANSALPHA)
	Entity:SetColor(255, 255, 255, 100)
	
	return Entity
end

// Get first entry

function ContraptionSaver.GetFirstEntry(Table)
	local Key, Value = next(Table)
	
	return Value
end

// Freeze entity

function ContraptionSaver.FreezeEntity(Entity)
	local Count = Entity:GetPhysicsObjectCount()
	
	for I = 1, Count do
		local Phys = Entity:GetPhysicsObjectNum(I)
		
		if (Phys) then
			local Valid = Phys:IsValid()
			
			if (Valid) then Phys:EnableMotion(false) end
		end
	end
end

// Player connected

function ContraptionSaver.PlayerConnected(Player)
	local Connected = Player:IsConnected()
	
	return Connected
end

// Paste entity

function ContraptionSaver.PasteEntity(Player, Entity)
	// It's a ContraptionSaver entity
	
	Entity.ContraptionSaver = true
	
	// Create it
	
	local Created = duplicator.CreateEntityFromTable(Player, Entity)
	
	if (Created) then
		Created.BoneMods = Entity.BoneMods
		Created.EntityMods = Entity.EntityMods
		Created.PhysicsObjects = Entity.PhysicsObjects
	end
	
	return Created
end

// Spawn entity

function ContraptionSaver.SpawnEntity(Player, Key, Entity, Entities, Max)
	// Connected
	
	if not (ContraptionSaver.PlayerConnected(Player)) then return end
	
	// Create the entity
	
	Entity = ContraptionSaver.PasteEntity(Player, Entity)
	
	// Check the entity exists
	
	if not (Entity) then return false end
	
	// Freeze the new entity
	
	ContraptionSaver.FreezeEntity(Entity)
	
	// Add it to the table
	
	Entities[Key] = Entity
	
	// Message
	
	local Percentage = ContraptionSaver.GetPercentageSpawned(Max, table.Count(Entities))
	
	Player:PrintMessage(4, "Entities: "..Percentage.."%")
end

// Spawn constraint

function ContraptionSaver.SpawnConstraint(Player, Constraint, Entities, Constraints, Max)
	// Connected
	
	if not (ContraptionSaver.PlayerConnected(Player)) then return end
	
	// Create the constraint
	
	local Entity = duplicator.CreateConstraintFromTable(Constraint, Entities)
	
	// Check the entity exists
	
	if not (Entity) then return false end
	
	// Insert constraint
	
	table.insert(Constraints, Entity)
	
	// Message
	
	local Percentage = ContraptionSaver.GetPercentageSpawned(Max, table.Count(Constraints))
	
	Player:PrintMessage(4, "Constraints: "..Percentage.."%")
end

// Get percentage spawned

function ContraptionSaver.GetPercentageSpawned(Max, Done)
	local Percentage = (100 / Max) * Done
	
	Percentage = math.floor(Percentage)
	
	Percentage = math.Clamp(Percentage, 0, 100)
	
	return Percentage
end

// Paste in parts

function ContraptionSaver.SpawnInParts(Player, Table)
	local Entities = {}
	local Constraints = {}
	
	// Max
	
	local Max = {}
	
	Max.Entities = table.Count(Table.Entities)
	Max.Constraints = table.Count(Table.Constraints)
	
	// Spawning
	
	ContraptionSaver.Players[Player].Spawning.Delay = 0
	ContraptionSaver.Players[Player].Spawning.Active = true
	
	// SpamSecure
	
	ContraptionSaver.SpamSecureProtect(Player, true)
	
	// Paste entities in parts
	
	for K, V in pairs(Table.Entities) do
		ContraptionSaver.Timers.Add(ContraptionSaver.Players[Player].Spawning.Delay, ContraptionSaver.SpawnEntity, Player, K, V, Entities, Max.Entities)
		
		ContraptionSaver.Players[Player].Spawning.Delay = ContraptionSaver.Players[Player].Spawning.Delay + ContraptionSaver.Config.Delay
	end
	
	// Paste constraints in parts
	
	for K, V in pairs(Table.Constraints) do
		ContraptionSaver.Timers.Add(ContraptionSaver.Players[Player].Spawning.Delay, ContraptionSaver.SpawnConstraint, Player, V, Entities, Constraints, Max.Constraints)
		
		ContraptionSaver.Players[Player].Spawning.Delay = ContraptionSaver.Players[Player].Spawning.Delay + ContraptionSaver.Config.Delay
	end
	
	// End
	
	local function End(Player, Entities, Constraints)
		if not (ContraptionSaver.PlayerConnected(Player)) then
			ContraptionSaver.RemoveEntities(Entities)
			ContraptionSaver.RemoveEntities(Constraints)
			
			return
		end
		
		// Message
		
		Player:PrintMessage(4, "Contraption: 100%")
		
		// Finish spawning entities
		
		ContraptionSaver.FinishSpawningEntities(Player, Entities, Constraints)
	end
	
	ContraptionSaver.Timers.Add(ContraptionSaver.Players[Player].Spawning.Delay, End, Player, Entities, Constraints)
end

// Finish spawning entities

function ContraptionSaver.FinishSpawningEntities(Player, Entities, Constraints)
	// Post entities spawned and spam secure stuff
	
	ContraptionSaver.PostEntitiesSpawned(Player, Entities, Constraints)
	ContraptionSaver.SpamSecureProtect(Player, false)
	
	// No longer spawning
	
	ContraptionSaver.Players[Player].Spawning.Last = CurTime() + ContraptionSaver.Players[Player].Spawning.Delay
	ContraptionSaver.Players[Player].Spawning.Active = false
	ContraptionSaver.Players[Player].Spawning.Delay = 0
	
	// Create ghost entities
	
	if (ContraptionSaver.GetCurrentTool(Player) == "contraptionsaver") then
		ContraptionSaver.Players[Player].Ghosts = ContraptionSaver.CreateGhostEntities(Player, ContraptionSaver.Players[Player].Loaded.Table)
	else
		ContraptionSaver.Unload(Player, true)
	end
end

// Get current tool

function ContraptionSaver.GetCurrentTool(Player)
	if (Player:GetActiveWeapon() == Player:GetWeapon("gmod_tool")) then
		return Player:GetInfo("gmod_toolmode")
	else
		return false
	end
end

// Post entities spawned

function ContraptionSaver.PostEntitiesSpawned(Player, Entities, Constraints)
	// Undo
	
	undo.Create("contraption")
	
	// Entities
	
	for K, V in pairs(Entities) do
		local Valid = V:IsValid()
		
		if (Valid) then
			ContraptionSaver.SpawnSelected(Player, V)
			ContraptionSaver.CreateUndo(Player, V)
			ContraptionSaver.UnfreezeEntities(Player, V)
			ContraptionSaver.PostEntityPaste(Player, V, Entities)
			ContraptionSaver.ApplyEntityModifiers(Player, V)
		end
	end
	
	// Constraints
	
	for K, V in pairs(Constraints) do
		local Valid = V:IsValid()
		
		if (Valid) then
			ContraptionSaver.CreateUndo(Player, V)
		end
	end
	
	// Undo
	
	undo.SetPlayer(Player)
	undo.Finish()
end

// Spawn selected

function ContraptionSaver.SpawnSelected(Player, Entity)
	if (Player:GetInfoNum("contraptionsaver_Spawn_Selected", 0) == 1) then
		ContraptionSaver.Select(Player, Entity)
	end
end

// Apply entity modifiers

function ContraptionSaver.ApplyEntityModifiers(Player, Entity)
	duplicator.ApplyEntityModifiers(Player, Entity)
	duplicator.ApplyBoneModifiers(Player, Entity)
end

// Post entity paste

function ContraptionSaver.PostEntityPaste(Player, Entity, Entities)
	if (Entity.PostEntityPaste) then
		Entity:PostEntityPaste(Player, Entity, Entities)
	end
end

// Remove entities

function ContraptionSaver.RemoveEntities(Entities)
	for K, V in pairs(Entities) do
		local Valid = V:IsValid()
		
		if (Valid) then
			V:Remove()
		end
	end
end

// Unfreeze entities

function ContraptionSaver.UnfreezeEntities(Player, Entity)
	local Frozen = Player:GetInfoNum("contraptionsaver_Spawn_Frozen", 0)
	
	// Check if frozen is 0
	
	if (Frozen == 0) then
		local Count = Entity:GetPhysicsObjectCount()
		
		for I = 1, Count do
			local Phys = Entity:GetPhysicsObjectNum(I)
			
			if (Phys) then
				local Valid = Phys:IsValid()
				
				if (Valid) then
					Phys:EnableMotion(true)
					Player:AddFrozenPhysicsObject(Entity, Phys)
				end
			end
		end
	end
end

// SpamSecure

function ContraptionSaver.SpamSecureProtect(Player, Bool)
	if (SpamSecure) then
		if (SpamSecure.Protect) then
			SpamSecure.Protect(Player, "ContraptionSaver", Bool)
		end
	end
end

// Get cached contraption

function ContraptionSaver.GetCachedContraption(Path, Contents)
	local Table = false
	
	if (ContraptionSaver.Cached[Path]) then
		if (ContraptionSaver.Cached[Path][2] == Contents) then
			Table = ContraptionSaver.Cached[Path][1]
		end
	end
	
	if not (Table) then
		Table = ContraptionSaver.CacheContraption(Path, Contents)
		
		// Message
		
		if (Table) then
			ContraptionSaver.MessageAll(3, '[Contraption Saver] Caching the contraption: '..string.format("%q", Path)..'!')
		end
	end
	
	if (Table) then
		return table.Copy(Table)
	else
		return false
	end
end

// Spawning contraption

function ContraptionSaver.SpawningContraption(Player)
	if (ContraptionSaver.Players[Player].Spawning.Active) then
		return true
	end
	
	return false
end

// Get deserialised table

function ContraptionSaver.GetDeserialisedTable(Contents)
	local Header, Extra, Data = Serialise.DeserialiseWithHeaders(Contents)
	
	// Data
	
	if not (Data) then return false end
	
	// Check
	
	if (Header.Description) then Header.Description = string.Replace(Header.Description, '"', '') end
	if (Header.Creator) then Header.Creator = string.Replace(Header.Creator, '"', '') end
	
	// Information
	
	Data.Information = {}
	Data.Information.Creator = Header.Creator or "Unknown"
	Data.Information.Date = Header.Date or "Unknown"
	Data.Information.Description = Header.Description or "Unknown"
	Data.Information.Entities = Header.Entities or 0
	Data.Information.Constraints = Header.Constraints or 0
	Data.Information.Version = Extra.Version
	
	// Return the data
	
	return Data
end

// Load entities

function ContraptionSaver.File.Load(Table, Path, File)
	// Loaded table and Contents string
	
	local Contents = Path
	local Loaded = {}
	
	// File
	
	if (File) then
		Contents = file.Read(Path)
		
		Loaded = ContraptionSaver.GetCachedContraption(Path, Contents)
		
		// Check if it needs converting
		
		if not (Loaded) then
			local B, Error = pcall(ContraptionSaver.ConvertFile, Path, Contents)
			
			if (B) then
				return ContraptionSaver.File.Load(Table, Path, File), "This file has been converted from an old format!"
			else
				return false, "This file is using an unknown format and cannot be converted!"
			end
		end
	else
		Loaded = ContraptionSaver.GetDeserialisedTable(Path)
	end
	
	// Check is it is valid
	
	if not (Loaded) then return false, "This file is using an unknown format!" end
	
	// Entities and constraints incase they do not exist
	
	Loaded.Entities = Loaded.Entities or {}
	Loaded.Constraints = Loaded.Constraints or {}
	
	// Add any other information we want to add
	
	Loaded.Information.Size = math.ceil(string.len(Contents) / 1024)
	
	// Table
	
	Table.Loaded = {}
	Table.Loaded.Path = Path
	Table.Loaded.Table = Loaded
	
	return Table, "This file has been loaded successfully!"
end

// Unload

function ContraptionSaver.Unload(Player, Clear)
	if (ContraptionSaver.SpawningContraption(Player)) then return end
	
	// Remove ghost entities
	
	ContraptionSaver.RemoveGhostEntities(Player)
	
	// Loaded
	
	ContraptionSaver.Players[Player].Loaded = nil
	
	// Unload
	
	umsg.Start("ContraptionSaver.Unload", Player) umsg.End()
	
	// Clear
	
	if (Clear) then
		ContraptionSaver.Clear(Player)
	end
end

// Remove ghost entities

function ContraptionSaver.RemoveGhostEntities(Player)
	if (ContraptionSaver.Players[Player].Loaded) then
		if (ContraptionSaver.Players[Player].Ghosts) then
			for K, V in pairs(ContraptionSaver.Players[Player].Ghosts.Entities) do
				local Valid = V:IsValid()
				
				if (Valid) then
					V:Remove()
				end
			end
			
			// Head entity
			
			if (ContraptionSaver.Players[Player].Ghosts.Head) then
				local Valid = ContraptionSaver.Players[Player].Ghosts.Head[2]:IsValid()
				
				if (Valid) then
					ContraptionSaver.Players[Player].Ghosts.Head[2]:Remove()
				end
			end
			
			// Remove the table
			
			ContraptionSaver.Players[Player].Ghosts = nil
		end
	end
end

// Cache

function ContraptionSaver.CacheContraption(File, Contents)
	// Table
	
	local Table = ContraptionSaver.GetDeserialisedTable(Contents)
	
	// Cache
	
	if (Table) then
		ContraptionSaver.Cached[File] = {Table, Contents}
	end
	
	// Return
	
	return Table
end

// Message all

function ContraptionSaver.MessageAll(Type, Text)
	local Players = player.GetAll()
	
	for K, V in pairs(Players) do
		V:PrintMessage(Type, Text)
	end
end

// Add undo

function ContraptionSaver.CreateUndo(Player, Entity)
	// Entity
	
	undo.AddEntity(Entity)
	
	// Cleanup
	
	Player:AddCleanup("contraptions", Entity)
end

// Get lowest entity

function ContraptionSaver.GetHeadEntity(Entity, Table)
	if (Table.Head) then
		if (Table.Entities[Table.Head]) then
			return Table.Entities[Table.Head]
		end
	end
	
	// Return the found head entity
	
	return Entity
end

// Convert entity positions

function ContraptionSaver.ConvertEntityPositions(Player, Table, Trace)
	// Get the player's angles
	
	local Ang = Player:GetAngles() Ang.p = 0 Ang.r = 0
	
	// Get the head entity's height
	
	local Height = Table.Height or 0
	
	// Set entities positions
	
	for K, V in pairs(Table.Entities) do
		// Position
		
		local P, A = LocalToWorld(Table.Entities[K].LocalPos, Table.Entities[K].LocalAngle, Trace.HitPos, Ang - ContraptionSaver.Players[Player].Loaded.Angle)
		
		P.z = P.z + Height
		
		// Set the position and the angle
		
		Table.Entities[K].Pos = P
		Table.Entities[K].Angle = A
		
		// Physics objects
		
		if (V.PhysicsObjects) then
			for B, J in pairs(V.PhysicsObjects) do
				// Position
				
				local P, A = LocalToWorld(Table.Entities[K].PhysicsObjects[B].LocalPos, Table.Entities[K].PhysicsObjects[B].LocalAngle, Trace.HitPos, Ang - ContraptionSaver.Players[Player].Loaded.Angle)
				
				P.z = P.z + Height
				
				// Set the position and the angle
				
				Table.Entities[K].PhysicsObjects[B].Pos = P
				Table.Entities[K].PhysicsObjects[B].Angle = A
			end
		end
	end
end

// Conver constraint positions

function ContraptionSaver.ConvertConstraintPositions(Player, Table, Trace, Position)
	// Get the player's angles
	
	local Ang = Player:GetAngles() Ang.p = 0 Ang.r = 0
	
	// Get the head entity's height
	
	local Height = Table.Height or 0
	
	// Set entities positions
	
	for K, V in pairs(Table.Constraints) do
		if (V.Entity) then
			for B, J in pairs(V.Entity) do
				if (J.World and J.LPos) then
					// Set the origin
					
					if not (Table.Constraints[K].Entity[B].Origin) then
						Table.Constraints[K].Entity[B].Origin = Table.Constraints[K].Entity[B].LPos
					end
					
					// Position
					
					local P, A = LocalToWorld(Table.Constraints[K].Entity[B].Origin, Angle(0, 0, 0), Trace.HitPos, Ang - ContraptionSaver.Players[Player].Loaded.Angle)
					
					P.z = P.z + Height
					
					// Set the position
					
					Table.Constraints[K].Entity[B].LPos = P
				end
			end
		end
	end
end

// Table to key values

function ContraptionSaver.TableToKeyValues(Table)
	local function Convert(Table, Converted, New)
		// Converted
		
		Converted = Converted or {}
		
		// New
		
		New = New or {}
		
		// Core
		
		for K, V in pairs(Table) do
			if (type(V) == "table" and not Converted[K]) then
				Converted[K] = true
				
				New[ContraptionSaver.CaseProtect(K)] = Convert(V)
			else
				if not (Converted[K]) then
					Converted[K] = true
					
					if (type(V) == "boolean") then
						V = "B"..tostring(V)
					elseif (type(V) == "Vector") then
						V = "V"..V.x.." "..V.y.." "..V.z
					elseif (type(V) == "Angle") then
						V = "A"..V.p.." "..V.y.." "..V.r
					elseif (type(V) == "number") then
						V = "N"..tostring(V)
					elseif (type(V) == "string") then
						V = "S"..tostring(V)
					end
					
					New[ContraptionSaver.CaseProtect(K)] = V
				end
			end
		end
		
		return New
	end
	
	local Return = Convert(Table)
	
	return util.TableToKeyValues(Return)
end

// Key values to table

function ContraptionSaver.KeyValuesToTable(String)
	// Check if it is key values
	
	if not (string.Left(String, 5) == '"Out"') then return false end
	
	// Convert it
	
	local function Convert(Table, Converted, New)
		Converted = Converted or {}
		
		// New
		
		New = New or {}
		
		// Core
		
		for K, V in pairs(Table) do
			if (type(V) == "table" and not Converted[K]) then
				Converted[K] = true
				
				New[ContraptionSaver.CaseUnprotect(K)] = Convert(V)
			else
				if not (Converted[K]) then
					Converted[K] = true
					
					if (string.sub(V, 0, 1) == "B") then
						V = string.sub(V, 2)
						
						if (V == "true") then V = true end
						if (V == "false") then V = false end
					elseif (string.sub(V, 0, 1) == "V") then
						V = string.sub(V, 2)
						
						local Explode = string.Explode(" ", V)
						
						Explode[1] = tonumber(Explode[1])
						Explode[2] = tonumber(Explode[2])
						Explode[3] = tonumber(Explode[3])
						
						V = Vector(Explode[1], Explode[2], Explode[3])
					elseif (string.sub(V, 0, 1) == "A") then
						V = string.sub(V, 2)
						
						local Explode = string.Explode(" ", V)
						
						Explode[1] = tonumber(Explode[1])
						Explode[2] = tonumber(Explode[2])
						Explode[3] = tonumber(Explode[3])
						
						V = Angle(Explode[1], Explode[2], Explode[3])
					elseif (string.sub(V, 0, 1) == "N") then
						V = string.sub(V, 2)
						
						V = tonumber(V)
					elseif (string.sub(V, 0, 1) == "S") then
						V = string.sub(V, 2)
					end
					
					New[ContraptionSaver.CaseUnprotect(K)] = V
				end
			end
		end
		
		return New
	end
	
	local Table = util.KeyValuesToTable(String)
	
	local Return = Convert(Table)
	
	return Return
end

// Case protect (Thanks to Tad2020)

function ContraptionSaver.CaseProtect(String)
	local New = ""
	
	// Numeric
	
	if (type(String) == "number") then return "#"..tostring(String) end
	
	// Fix
	
	for I = 1, string.len(String) do
		local Character = string.sub(String, I, I)
		
		if (Character != string.lower(Character)) then Character = "^"..Character end
		
		New = New..Character
	end
	
	return New
end

// Case unprotect (Thanks to Tad2020)

function ContraptionSaver.CaseUnprotect(String)
	local New = ""
	
	// Numeric
	
	if (string.sub(String, 1, 1) == "#") then return tonumber(string.sub(String, 2)) end
	
	// Fix
	
	for I = 1, string.len(String) do
		local Character = string.sub(String, I, I)
		
		if (string.sub(String, I - 1, I - 1) == "^") then Character = string.upper(Character) end
		
		if (Character != "^") then New = New..Character end
	end
	
	return New
end

// Reduce entity file size

function ContraptionSaver.ReduceEntityFileSize(Entity)
	local Class = duplicator.FindEntityClass(Entity.Class)
	
	// New
	
	local New = {}
	
	New.Class = Entity.Class
	
	if (Entity.Model) then New.Model = Entity.Model end
	if (Entity.Angle) then New.Angle = Entity.Angle end
	if (Entity.Pos) then New.Pos = Entity.Pos end
	if (Entity.LocalPos) then New.LocalPos = Entity.LocalPos end
	if (Entity.LocalAngle) then New.LocalAngle = Entity.LocalAngle end
	
	// No custom class
	
	if (!Class) then return New end
	
	// Loop through args
	
	for K, V in pairs(Class.Args) do
		New[V] = Entity[V]
	end
	
	return New
end

// Convert constraint position to local

function ContraptionSaver.ConvertConstraintPositionToLocal(Origin, Constraint)
	if (Constraint.Entity) then
		for B, J in pairs(Constraint.Entity) do
			if (Constraint.Entity[B].World and Constraint.Entity[B].LPos) then
				local Position = Constraint.Entity[B].LPos - Origin
				
				Constraint.Entity[B].LPos = Position * 1
			end
		end
	end
end

// Convert entity position to local

function ContraptionSaver.ConvertEntityPositionToLocal(Origin, Entity)
	local Position = Entity.Pos - Origin
	
	// Get the local position and local angle
	
	Entity.LocalPos = Position * 1
	Entity.LocalAngle = Entity.Angle * 1
	
	// Convert physics objects
	
	if (Entity.PhysicsObjects) then
		for B, J in pairs(Entity.PhysicsObjects) do
			local Position = Entity.PhysicsObjects[B].Pos - Origin
			
			Entity.PhysicsObjects[B].LocalPos = Position * 1
			Entity.PhysicsObjects[B].LocalAngle = Entity.PhysicsObjects[B].Angle * 1
		end
	end
end

// Get height from the ground to an entity's position

function ContraptionSaver.GetHeight(Entity)
	local Position = Entity:GetPos()
	
	// Get a trace
	
	local TR = {}
	
	// Variables (Take from the Duplicator)
	
	TR.start = Position
	TR.endpos = Position + Vector(0, 0, -1024)
	TR.mask = MASK_NPCSOLID_BRUSHONLY
	
	// Floor
	
	local Floor = util.TraceLine(TR)
	
	if (Floor.Hit) then Position = Vector(0, 0, -1) * Floor.Fraction * 1024 end
	
	// Return the Z position
	
	return math.abs(Position.z)
end

// Convert to saveable

function ContraptionSaver.ConvertToSaveableTable(Table)
	local Saveable = {}
	
	// Create the entities and constraints tables
	
	Saveable.Entities = {}
	Saveable.Constraints = {}
	
	// Head entity
	
	local Head = ContraptionSaver.GetFirstEntry(Table.Entities)
	
	// Thanks to TAD2020 for an alternative method to reduce lag
	
	local Constraints = {}
	
	// Find the lowest entity
	
	for K, V in pairs(Table.Entities) do
		local Valid = V:IsValid()
		
		if (Valid) then
			// Lowest entity
			
			if (V.ContraptionSaver and V.ContraptionSaver.Head) then Head = V end
			
			// Get all constrained entities and constraints
			
			duplicator.GetAllConstrainedEntitiesAndConstraints(V, {}, Constraints)
		end
	end
	
	// Copy entities
	
	for K, V in pairs(Table.Entities) do
		local Valid = V:IsValid()
		
		if (Valid) then
			local Copy = duplicator.CopyEntTable(V)
			
			// Conver entity position to local
			
			local Position = Head:GetPos() ContraptionSaver.ConvertEntityPositionToLocal(Position, Copy)
			
			// Reduce entity file size
			
			Saveable.Entities[K] = ContraptionSaver.ReduceEntityFileSize(Copy)
			
			// Head
			
			if (Head == V) then Saveable.Head = K Saveable.Height = ContraptionSaver.GetHeight(Head) end
			
			// Save entity hook
			
			ContraptionSaver.Hooks.Call("Save Entity", V, Copy, Saveable.Entities[K])
			
			// Modifiers
			
			Saveable.Entities[K].BoneMods = Copy.BoneMods
			Saveable.Entities[K].EntityMods = Copy.EntityMods
			Saveable.Entities[K].PhysicsObjects = Copy.PhysicsObjects
		end
	end
	
	// Constraints
	
	for K, V in pairs(Constraints) do
		local Constraint = ContraptionSaver.ReduceConstraintFileSize(V)
		
		// Check constraint
		
		if (Constraint) then
			// Convert constraint position to local
			
			local Position = Head:GetPos() ContraptionSaver.ConvertConstraintPositionToLocal(Position, Constraint)
			
			// Insert the constraint into the constraints table
			
			table.insert(Saveable.Constraints, Constraint)
		end
	end
	
	return Saveable
end

// Reduce 

function ContraptionSaver.ReduceConstraintFileSize(Constraint)
	local Type = duplicator.ConstraintType[Constraint.Type]
	
	if (!Type) then return false end
	
	// New constraint
	
	local New = {}
	
	New.Type = Constraint.Type
	New.Entity = Constraint.Entity
	
	// Loop through args
	
	for K, V in pairs(Type.Args) do
		if (not string.find(V, "Ent") or string.len(V) != 4) and (not string.find(V, "Bone") or string.len(V) != 5) then
			New[V] = Constraint[V]
		end
	end
	
	return New
end

// Commands

ContraptionSaver.Commands = {}

// Commands finish

function ContraptionSaver.Commands.Finish(Player, Command, Args)
	if not (Args[1]) then return end
	
	if (Args[1] == "Downloading") then
		ContraptionSaver.Players[Player].Downloading.Active = false
		ContraptionSaver.Players[Player].Downloading.Last = CurTime() + ContraptionSaver.Config.Download
	end
end

concommand.Add("contraptionsaver_Commands_Finish", ContraptionSaver.Commands.Finish)

// Commands download

function ContraptionSaver.Commands.Download(Player, Command, Args)
	local File, Directory = ContraptionSaver.GetFilePath(Player)
	
	if (File) then
		// Current time since game started
		
		local Cur = CurTime()
		
		// Check if player has a download active
		
		if (ContraptionSaver.Players[Player].Downloading.Active) then
			ContraptionSaver.Message(Player, 'You are already downloading the contraption '..string.format("%q", ContraptionSaver.Players[Player].Downloading)..'!')
			
			return
		end
		
		// Last downloaded contraption check
		
		if (ContraptionSaver.Players[Player].Downloading.Last > Cur) then
			local Time = ContraptionSaver.Players[Player].Downloading.Last - Cur
			
			Time = math.floor(Time)
			
			if (Time != 0) then
				ContraptionSaver.Message(Player, 'Please wait '..Time..' seconds to download another contraption!')
				
				return
			end
		end
		
		// Check it exists
		
		local Exists = file.Exists(ContraptionSaver.Config.Directory..Directory..File)
		
		if not (Exists) then
			ContraptionSaver.Message(Player, 'The contraption '..string.format("%q", Directory..File)..' does not exist!')
			
			return
		end
		
		// Main
		
		umsg.Start("ContraptionSaver.Download", Player)
			umsg.Short(1)
			umsg.String(ContraptionSaver.Config.Directory)
		umsg.End()
		
		// Directory
		
		umsg.Start("ContraptionSaver.Download", Player)
			umsg.Short(2)
			umsg.String(Directory)
		umsg.End()
		
		// File
		
		umsg.Start("ContraptionSaver.Download", Player)
			umsg.Short(3)
			umsg.String(File)
		umsg.End()
		
		// Message
		
		ContraptionSaver.MessageAll(3, '[Contraption Saver] '..Player:Name()..' is downloading the contraption '..string.format("%q", Directory..File)..'!')
		
		// Read
		
		local Contents = file.Read(ContraptionSaver.Config.Directory..Directory..File)
		
		// Size
		
		umsg.Start("ContraptionSaver.Download", Player)
			umsg.Short(4)
			umsg.String(string.len(Contents))
		umsg.End()
		
		// Explode
		
		local Explode = string.Explode("\n", Contents)
		
		local function Send(String, Player)
			local Len = string.len(String)
			
			if (Len > 200) then
				local One = string.sub(String, 0, 199)
				local Two = string.sub(String, 200)
				
				Send(One, Player)
				Send(Two, Player)
			else
				umsg.Start("ContraptionSaver.Download", Player)
					umsg.Short(5)
					umsg.String(String)
				umsg.End()
			end
		end
		
		// Send
		
		for K, V in pairs(Explode) do
			V = V.."\n"
			
			Send(V, Player)
		end
		
		// Finish
		
		umsg.Start("ContraptionSaver.Download", Player)
			umsg.Short(6)
		umsg.End()
		
		// Downloading
		
		ContraptionSaver.Players[Player].Downloading.Active = Directory..File
	end
end

concommand.Add("contraptionsaver_Commands_Download", ContraptionSaver.Commands.Download)

// Commands generate

function ContraptionSaver.Commands.Generate(Player)
	ContraptionSaver.GenerateDirectories(Player)
	
	// Message
	
	ContraptionSaver.Message(Player, "The directories list has been updated!")
end

concommand.Add("contraptionsaver_Commands_Generate", ContraptionSaver.Commands.Generate)

// Commands delete

function ContraptionSaver.Commands.Delete(Player, Command, Args)
	if (Args and Args[1] and Args[1] == "Directory") then
		local File, Directory = ContraptionSaver.GetFilePath(Player)
		
		if (Directory and file.IsDir(ContraptionSaver.Config.Directory..Directory)) then
			local Files = file.Find(ContraptionSaver.Config.Directory..Directory.."*.*")
			local Count = 0
			
			for K, V in pairs(Files) do
				ContraptionSaver.File.Delete(ContraptionSaver.Config.Directory..Directory..V)
				
				Count = Count + 1
			end
			
			ContraptionSaver.Message(Player, Count..' contraptions deleted from '..string.format("%q", Directory)..'!')
			
			// Generate directories
			
			ContraptionSaver.GenerateDirectories(Player)
		else
			ContraptionSaver.Message(Player, 'The directory '..string.format("%q", Directory)..' does not exist!')
		end
		
		return
	end
	
	local File, Directory = ContraptionSaver.GetFilePath(Player)
	
	if (File) then
		local Exists = file.Exists(ContraptionSaver.Config.Directory..Directory..File)
		
		if not (Exists) then
			ContraptionSaver.Message(Player, 'Contraption '..string.format("%q", Directory..File)..' does not exist!')
			
			return
		end
		
		// Check if it belongs to player
		
		if not (ContraptionSaver.IsDirectoryOwner(Player, Directory)) then return end
		
		// Delete
		
		ContraptionSaver.File.Delete(ContraptionSaver.Config.Directory..Directory..File)
		
		// Generate directories
		
		ContraptionSaver.GenerateDirectories(Player)
		
		// Message
		
		ContraptionSaver.Message(Player, 'Contraption '..string.format("%q", Directory..File)..' has been deleted!')
	end
end

concommand.Add("contraptionsaver_Commands_Delete", ContraptionSaver.Commands.Delete)

// Commands Save

function ContraptionSaver.Commands.Save(Player, Command, Args)
	local File, Directory = ContraptionSaver.GetFilePath(Player)
	
	if (File) then
		// File length
		
		if (string.len(File) > 75) then
			ContraptionSaver.Message(Player, "Your file name is too long!")
			
			return
		end
		
		// Directory length
		
		if (string.len(Directory) > 75) then
			ContraptionSaver.Message(Player, "Your directory name is too long!")
			
			return
		end
		
		// Check extension
		
		if not (string.sub(File, -4) == ".txt") then
			File = File..".txt"
		end
		
		// Check if it belongs to player
		
		if not (ContraptionSaver.IsDirectoryOwner(Player, Directory)) then return end
		
		// Check if it exists
		
		local Exists = file.Exists(ContraptionSaver.Config.Directory..Directory..File)
		
		if (Exists) then
			ContraptionSaver.Message(Player, 'The contraption '..string.format("%q", Directory..File)..' already exists!')
			
			return
		end
		
		// Are there no entities to save
		
		local Count = table.Count(ContraptionSaver.Players[Player].Entities)
		
		if (Count == 0) then
			ContraptionSaver.Message(Player, 'You have no entities selected to save!')
			
			return
		end
		
		// Description
		
		local Description = Player:GetInfo("contraptionsaver_Description")
		
		if (string.len(Description) > 75) then
			ContraptionSaver.Message(Player, "Your description is too long!")
			
			return
		end
		
		// Message all
		
		ContraptionSaver.MessageAll(3, '[Contraption Saver] '..Player:Name()..' is saving the contraption '..string.format("%q", ContraptionSaver.Config.Directory..Directory..File)..'!')
		
		local function Save()
			ContraptionSaver.File.Save(ContraptionSaver.Players[Player], ContraptionSaver.Config.Directory..Directory..File, Player:Name(), Description)
			
			// Generate directories
			
			ContraptionSaver.GenerateDirectories(Player)
			
			// Save
			
			ContraptionSaver.Message(Player, "Contraption "..string.format("%q", ContraptionSaver.Config.Directory..Directory..File).." has been saved!")
			
			// Set owner
			
			ContraptionSaver.SetDirectoryOwner(Player, Directory)
		end
		
		ContraptionSaver.Timers.Add(0.5, Save)
	end
end

concommand.Add("contraptionsaver_Commands_Save", ContraptionSaver.Commands.Save)

// Commands spawn

function ContraptionSaver.Commands.Spawn(Player)
	if (ContraptionSaver.Players[Player].Loaded) then
		if (ContraptionSaver.SpawningContraption(Player)) then
			ContraptionSaver.Message(Player, 'You are already spawning a contraption!')
			
			return
		end
		
		// Last time
		
		if (ContraptionSaver.Players[Player].Spawning.Last > CurTime()) then
			local Time = ContraptionSaver.Players[Player].Spawning.Last - CurTime()
			
			Time = math.floor(Time)
			
			if (Time != 0) then
				ContraptionSaver.Message(Player, 'Please wait '..Time..' seconds before spawning another contraption!')
				
				return
			end
		end
		
		// Table
		
		local Table = ContraptionSaver.Players[Player].Loaded.Table
		
		// Trace
		
		local Trace = Player:GetEyeTrace()
		
		// Convert positions
		
		ContraptionSaver.ConvertConstraintPositions(Player, Table, Trace)
		
		// Spawn in parts
		
		local B, Error = pcall(ContraptionSaver.SpawnInParts, Player, Table)
		
		if (!B) then
			ContraptionSaver.Message(Player, 'There was an error spawning this contraption!')
			
			Player:PrintMessage(2, "Error spawning contraption: "..Error)
		end
	else
		ContraptionSaver.Message(Player, 'You do not have a contraption loaded!')
	end
end

concommand.Add("contraptionsaver_Commands_Spawn", ContraptionSaver.Commands.Spawn)

// Select contraption saver

function ContraptionSaver.SelectContraptionSaver(Player)
	// Check tool
	
	Player:ConCommand("gmod_toolmode contraptionsaver\n")
	Player:SelectWeapon("gmod_tool")
end

// Commands load

function ContraptionSaver.Commands.Load(Player, Command, Args)
	local Cur = CurTime()
	
	// Select contraptionsaver
	
	ContraptionSaver.SelectContraptionSaver(Player)
	
	// Load
	
	if (ContraptionSaver.Players[Player].Load > Cur) then
		local Time = (ContraptionSaver.Players[Player].Load - Cur)
		
		Time = math.floor(Time)
		
		if (Time != 0) then
			ContraptionSaver.Message(Player, 'Please wait '..Time..' seconds to load another contraption!')
			
			return
		end
	end
	
	// Check if they are current spawning a contraption
	
	if (ContraptionSaver.SpawningContraption(Player)) then
		ContraptionSaver.Message(Player, 'You are currently spawning a contraption!')
		
		return
	end
	
	// Load selected
	
	if (Args and Args[1] and Args[1] == "Selected") then
		local Count = table.Count(ContraptionSaver.Players[Player].Entities)
		
		if (Count == 0) then
			ContraptionSaver.Message(Player, "You do not have any entities selected to load!")
			
			return
		end
		
		// Message all
		
		local String = ContraptionSaver.File.Save(ContraptionSaver.Players[Player], false, "Temporary", "Temporary")
		
		// Load
		
		local Success, Error = ContraptionSaver.File.Load(ContraptionSaver.Players[Player], String, false)
		
		if (Success) then
			ContraptionSaver.FinishLoadingContraption(Player)
		else
			ContraptionSaver.Message(Player, Error)
		end
		
		return
	end
	
	// Load
	
	local File, Directory = ContraptionSaver.GetFilePath(Player)
	
	if (File) then
		local Path = ContraptionSaver.Config.Directory..Directory..File
		
		// Check if it exists
		
		local Exists = file.Exists(Path)
		
		if (Exists) then
			// Timers
			
			local Success, Error = ContraptionSaver.File.Load(ContraptionSaver.Players[Player], Path, true)
			
			if (Success) then
				local Success = ContraptionSaver.FinishLoadingContraption(Player)
				
				if (Success) then ContraptionSaver.Message(Player, 'Contraption "'..Path..'" has been loaded!') end
			else
				ContraptionSaver.Message(Player, Error)
			end
		else
			ContraptionSaver.Message(Player, 'Contraption "'..Path..'" does not exist!')
		end
	end
end

concommand.Add("contraptionsaver_Commands_Load", ContraptionSaver.Commands.Load)

// Finish loading contraption

function ContraptionSaver.FinishLoadingContraption(Player)
	// Table
	
	local Table = ContraptionSaver.Players[Player].Loaded.Table
	
	// Check the models
	
	for K, V in pairs(Table.Entities) do
		if (Table.Entities[K].Model) then
			Table.Entities[K].Model = ContraptionSaver.ConvertModel(Table.Entities[K].Model)
			
			if not (file.Exists("../"..Table.Entities[K].Model)) then
				ContraptionSaver.Message(Player, 'Contraption could not load due to missing models!')
				
				ContraptionSaver.Unload(Player, true)
				
				return false
			end
		end
	end

	// Usermessage
	
	umsg.Start("ContraptionSaver.Loaded", Player)
		umsg.String(Table.Information.Date)
		umsg.String(Table.Information.Creator)
		umsg.String(Table.Information.Description)
		umsg.String(Table.Information.Version)
		umsg.String(Table.Information.Size)
		umsg.Short(Table.Information.Entities)
		umsg.Short(Table.Information.Constraints)
	umsg.End() 
	
	// Angle
	
	local Ang = Player:GetAngles() Ang.p = 0 Ang.r = 0
	
	ContraptionSaver.Players[Player].Loaded.Angle = Ang
	
	// Ghost entities
	
	ContraptionSaver.Players[Player].Ghosts = ContraptionSaver.CreateGhostEntities(Player, Table)
	
	// Spam
	
	ContraptionSaver.Players[Player].Load = CurTime() + ContraptionSaver.Config.Load
	
	// Check ghost entities
	
	if not (ContraptionSaver.Players[Player].Ghosts) then
		ContraptionSaver.Message(Player, 'Contraption could not load due to corrupted ghost entities!')
		
		ContraptionSaver.Unload(Player, true)
		
		return false
	end
	
	// We loaded it fine
	
	return true
end

// Commands unload

function ContraptionSaver.Commands.Unload(Player)
	if not (ContraptionSaver.Players[Player].Loaded) then
		ContraptionSaver.Message(Player, "You do not have any contraption loaded!")
		
		return
	end
	
	// Check if player is spawning a contraption
	
	if (ContraptionSaver.SpawningContraption(Player)) then
		ContraptionSaver.Message(Player, "You are currently spawning a contraption!")
		
		return
	end
	
	// Unload
	
	ContraptionSaver.Unload(Player)
end

concommand.Add("contraptionsaver_Commands_Unload", ContraptionSaver.Commands.Unload)

// Initialize

function ContraptionSaver.Initialize()
	if (ContraptionSaver.Config.Precache) then
		local function Function(File)
			local Players = player.GetAll()
			
			if (table.Count(Players) == 0) then
				local Contents = file.Read(File)
				
				local Success = ContraptionSaver.CacheContraption(File, Contents)
				
				if (Success) then
					Msg('[Contraption Saver] Precached contraption: "'..File..'"!\n')
				end
			end
		end
		
		// Recursive find
		
		ContraptionSaver.RecursiveFind(ContraptionSaver.Config.Directory, Function)
	end
	
	// Call hook
	
	ContraptionSaver.Hooks.Call("Initialize")
	
	// Directories
	
	ContraptionSaver.Directories = {}
	
	// Check if it exists
	
	local Exists = file.Exists(ContraptionSaver.Config.Directory.."/Directories.txt")
	
	if (Exists) then
		local Read = file.Read(ContraptionSaver.Config.Directory.."/Directories.txt")
		
		local Explode = string.Explode("\n", Read)
		
		for K, V in pairs(Explode) do
			local Split = string.Explode("|", V)
			
			
			if (Split[1]) then
				local Directory = string.lower(Split[1])
				
				Split[1] = Directory
				
				ContraptionSaver.Directories[Directory] = Split
			end
		end
	end
end

hook.Add("Initialize", "ContraptionSaver.Initialize", ContraptionSaver.Initialize)

// Player disconnected

function ContraptionSaver.PlayerDisconnected(Player)
	if (ContraptionSaver.Players[Player]) then
		if (ContraptionSaver.Players[Player].Spawning) then
			ContraptionSaver.Players[Player].Spawning.Active = false
			
			// Unload
			
			ContraptionSaver.Unload(Player, true)
		end
	end
end

hook.Add("PlayerDisconnected", "ContraptionSaver.PlayerDisconnected", ContraptionSaver.PlayerDisconnected)

// Player initial spawn

function ContraptionSaver.PlayerInitialSpawn(Player)
	ContraptionSaver.Players[Player] = {}
	ContraptionSaver.Players[Player].Load = 0
	ContraptionSaver.Players[Player].Entities = {}
	ContraptionSaver.Players[Player].Downloading = {}
	ContraptionSaver.Players[Player].Downloading.Last = 0
	ContraptionSaver.Players[Player].Downloading.Active = false
	ContraptionSaver.Players[Player].Spawning = {}
	ContraptionSaver.Players[Player].Spawning.Delay = 0
	ContraptionSaver.Players[Player].Spawning.Last = 0
	ContraptionSaver.Players[Player].Spawning.Active = false
end

hook.Add("PlayerInitialSpawn", "ContraptionSaver.PlayerInitialSpawn", ContraptionSaver.PlayerInitialSpawn)